1 module hip.api.renderer.shadervar;
2 import hip.api.renderer.core;
3 import hip.api.renderer.shader;
4 import hip.api.graphics.color;
5 public import hip.api.renderer.shadervar;
6 
7 /**
8 *   Changes how the Shader behaves based on the backend
9 */
10 enum ShaderHint : uint
11 {
12     NONE = 0,
13     GL_USE_BLOCK = 1<<0,
14     GL_USE_STD_140 = 1<<1,
15     D3D_USE_HLSL_4 = 1<<2,
16     /**
17      * Meant for usage in uniform variables.
18      * That means one Shader Variable may not be sent to the backend depending on its requirements.
19      * An example for that is Array of Textures. In D3D11, it depends only on the resource being bound,
20      * while on Metal and GL3, they are required to be inside a MTLBuffer or being sent as an Uniform.
21      */
22     Blackbox = 1 << 3,
23     MaxTextures = 1 << 4
24 }
25 
26 /**
27 *   Should not be used directly. The D type inference can already set that for you.
28 *   This is stored by the variable to know how to access itself and comunicate the shader.
29 */
30 enum UniformType : ubyte
31 {
32     boolean,
33     integer,
34     integer_array,
35     uinteger,
36     uinteger_array,
37     floating,
38     floating2,
39     floating3,
40     floating4,
41     floating2x2,
42     floating3x3,
43     floating4x4,
44     floating_array,
45     ///Special type that is implemented by renderers backend
46     texture_array,
47     none
48 }
49 
50 UniformType uniformTypeFrom(T)()
51 {
52     import hip.util.reflection;
53 
54     with(UniformType)
55     {
56         static if(is(T == bool)) return boolean;
57         else static if(is(T == int)) return integer;
58         else static if(is(T == int[])) return integer_array;
59         else static if(is(T == uint) || is(T == HipColor)) return uinteger;
60         else static if(is(T == uint[])) return uinteger_array;
61         else static if(is(T == float)) return floating;
62         else static if(is(T == float[2])) return floating2;
63         else static if(is(T == float[3])) return floating3;
64         else static if(is(T == float[4]) || is(T == HipColorf)) return floating4;
65         else static if(isTypeArrayOf!(float, T, 9)) return floating3x3;
66         else static if(isTypeArrayOf!(float, T, 16)) return floating4x4;
67         else static if(is(T == float[])) return floating_array;
68         else static if(is(T == IHipTexture[])) return texture_array;
69         else return none;
70     }
71 }
72 
73 /**
74 *   Struct that holds uniform/cbuffer information for Direct3D and OpenGL shaders. It can be any type.
75 *   Its data is accessed by the ShaderVariableLayout when sendVars is called. Thus, depending on its
76 *   corrensponding type, its data is uploaded to the GPU.
77 */
78 struct ShaderVar
79 {
80     import hip.util.data_structures:Array;
81     import std.traits;
82     void[] data;
83     string name;
84     ShaderTypes shaderType;
85     UniformType type;
86     size_t singleSize;
87     bool isDynamicArrayReference;
88     bool isDirty = true;
89 
90     ShaderHint flags;
91     ShaderVariablesLayout layout;
92     public bool isBlackboxed() const { return (flags & ShaderHint.Blackbox) != 0;}
93     public bool usesMaxTextures() const { return (flags & ShaderHint.MaxTextures) != 0;}
94 
95 
96     size_t varSize() const{return data.length;}
97     size_t length() const {return varSize / singleSize;}
98 
99     const T get(T)()
100     {
101         static if(isDynamicArray!T)
102             return cast(T)data;
103         else
104             return *(cast(T*)this.data.ptr);
105     }
106 
107 
108     private void setDirty()
109     {
110         this.isDirty = true;
111         this.layout.isDirty = true;
112     }
113 
114     bool setBlackboxed(T)(T value)
115     {
116         import core.stdc.string;
117         if(value.sizeof != varSize || !isBlackboxed) return false;
118         setDirty();
119         memcpy(data.ptr, &value, varSize);
120         return true;
121     }
122     bool set(T)(T value, bool validateData)
123     {
124         import core.stdc.string;
125         static assert(uniformTypeFrom!T != UniformType.none, "Invalid type "~T.stringof);
126         static if(isDynamicArray!T)
127         {
128             memcpy(data.ptr, value.ptr, value.length * T.init[0].sizeof);
129         }
130         else
131         {
132             if(value.sizeof != varSize)
133                 return false;
134             if(!isBlackboxed && validateData)
135             {
136                 import hip.math.matrix;
137                 auto current = get!T;
138                 static if(is(T == Matrix3) || is(T == Matrix4))
139                 {
140                     if(value == current || value == current.transpose)
141                         return true;
142                 }
143                 else
144                 {
145                     if(value == current)
146                         return true;
147                 }
148             }
149             memcpy(data.ptr, &value, varSize);
150         }
151         setDirty();
152         return true;
153     }
154 
155     private void throwOnOutOfBounds(size_t index)
156     {
157         import hip.util.conv:to;
158         switch(type) with(UniformType)
159         {
160             case boolean, integer, uinteger, floating:
161                 throw new Exception("Unsupported type '"~type.to!string~"' for indexing on shader vairable "~name);
162             default:
163                 if(index >= this.length)
164                     throw new Exception("Index "~index.to!string~" ot of range [0.."~length.to!string~"] on shader variable "~name);
165         }
166     }
167 
168     auto opIndexAssign(T)(T value, size_t index)
169     {
170         import hip.util.conv:to;
171         import core.stdc.string;
172         throwOnOutOfBounds(index);
173         if(index * singleSize + T.sizeof > varSize)
174         {
175             throw new Exception("Value assign of type "~T.stringof~" at index "~to!string(index)~
176             " is invalid for shader variable "~name~" of type "~to!string(type));
177         }
178 
179         memcpy(cast(ubyte*)data + singleSize*index, &value, T.sizeof);
180         return value;
181     }
182 
183     ref auto opIndex(size_t index)
184     {
185         throwOnOutOfBounds(index);
186         switch(type) with(UniformType)
187         {
188             case integer_array: return get!(int[])[index];
189             case uinteger_array: return get!(uint[])[index];
190             case floating_array: return get!(float[])[index];
191             case floating2: return get!(float[2])[index];
192             case floating3: return get!(float[3])[index];
193             case floating4: return get!(float[4])[index];
194             case floating2x2: return get!(float[4])[index];
195             case floating3x3: return get!(float[9])[index];
196             case floating4x4: return get!(float[16])[index];
197             default: return 0;
198         }
199     }
200 
201     static ShaderVar* create(ShaderTypes t, string varName, bool data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.boolean, data.sizeof, data.sizeof, layout);}
202     static ShaderVar* create(ShaderTypes t, string varName, int data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.integer, data.sizeof, data.sizeof, layout);}
203     static ShaderVar* create(ShaderTypes t, string varName, uint data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.uinteger, data.sizeof, data.sizeof, layout);}
204     static ShaderVar* create(ShaderTypes t, string varName, float data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating, data.sizeof, data.sizeof, layout);}
205     static ShaderVar* create(ShaderTypes t, string varName, float[2] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating2, data.sizeof, data[0].sizeof, layout);}
206     static ShaderVar* create(ShaderTypes t, string varName, float[3] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating3, data.sizeof, data[0].sizeof, layout);}
207     static ShaderVar* create(ShaderTypes t, string varName, float[4] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating4, data.sizeof, data[0].sizeof, layout);}
208     static ShaderVar* create(ShaderTypes t, string varName, float[9] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating3x3, data.sizeof, data[0].sizeof, layout);}
209     static ShaderVar* create(ShaderTypes t, string varName, float[16] data, ShaderVariablesLayout layout){return ShaderVar.create(t, varName, &data, UniformType.floating4x4, data.sizeof, data[0].sizeof, layout);}
210     static ShaderVar* create(ShaderTypes t, string varName, int[] data, ShaderVariablesLayout layout)
211     {
212         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, int.sizeof*data.length, int.sizeof, layout, true);
213     }
214     static ShaderVar* create(ShaderTypes t, string varName, uint[] data, ShaderVariablesLayout layout)
215     {
216         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, uint.sizeof*data.length, uint.sizeof, layout, true);
217     }
218     static ShaderVar* create(ShaderTypes t, string varName, float[] data, ShaderVariablesLayout layout)
219     {
220         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, float.sizeof*data.length, float.sizeof, layout, true);
221     }
222 
223     protected static ShaderVar* create(
224         ShaderTypes t,
225         string varName,
226         void* varData,
227         UniformType type,
228         size_t varSize,
229         size_t singleSize,
230         ShaderVariablesLayout layout,
231         bool isDynamicArrayReference=false
232     )
233     {
234         ShaderVar* s = createEmpty(t, varName, type, varSize, singleSize, layout);
235         s.data[0..varSize] = varData[0..varSize];
236         s.isDynamicArrayReference = isDynamicArrayReference;
237         return s;
238     }
239     public static ShaderVar* createEmpty(
240         ShaderTypes t,
241         string varName,
242         UniformType type,
243         size_t varSize,
244         size_t singleSize,
245         ShaderVariablesLayout layout
246     )
247     {
248         if(!isShaderVarNameValid(varName))
249             throw new Exception("Variable '"~varName~"' is invalid.");
250         ShaderVar* s = new ShaderVar();
251         if(varSize != 0)
252             s.data = new void[varSize];
253         s.name = varName;
254         s.singleSize = singleSize;
255         s.shaderType = t;
256         s.type = type;
257         s.layout = layout;
258         return s;
259     }
260 
261     void dispose()
262     {
263         type = UniformType.none;
264         shaderType = ShaderTypes.none;
265         singleSize = 0;
266         if(isDynamicArrayReference)
267         {
268             (cast(Array!(int)*)data).dispose();
269         }
270         else if(data != null)
271         {
272             import core.memory;
273             GC.free(data.ptr);
274             data = null;
275         }
276     }
277 }
278 
279 struct ShaderVarLayout
280 {
281     ShaderVar* sVar;
282     size_t alignment;
283     size_t size;
284 }
285 
286 /**
287 *   This class is meant to be created together with the Shaders.
288 *
289 *   Those are meant to wrap the cbuffer from Direct3D and Uniform Block from OpenGL.
290 *
291 *   By wrapping the uniforms/cbuffers layouts, it is much easier to send those variables from any API.
292 */
293 class ShaderVariablesLayout
294 {
295     import hip.api.renderer.var_packing;
296 
297     ShaderVarLayout[string] variables;
298     private string[] namesOrder;
299     private string[] unusedBlackboxed;
300     string name;
301     ///char* representation of name
302     const(char)* nameZeroEnded;
303     protected IShader owner;
304 
305     //Single block representation of variables content
306     protected void* data;
307     protected void* additionalData;
308 
309     ///The hint are used for the Shader backend as a notifier
310     public immutable int hint;
311     protected size_t lastPosition;
312 
313     ///A function that must return a variable size when position = 0
314     private VarPosition function(
315         ref ShaderVar* v,
316         size_t lastAlignment,
317         bool isLast
318     ) packFunc;
319 
320     ShaderTypes shaderType;
321     bool isDirty = true;
322     protected bool isAdditionalAllocated;
323     ///Can't unlock Layout
324     private bool isLocked;
325 
326     /**
327     *   Use the layout name for mentioning the uniform/cbuffer block name.
328     *
329     *   Its members are the ShaderVar* passed
330     *
331     *   Params:
332     *       layoutName = From which block it will be accessed on the shader
333     *       t = What is the shader type that holds those variables
334     *       hint = Use ShaderHint for additional information, multiple hints may be passed
335     *       variables = Usually you won't pass any and use .append for writing less
336     */
337     this(HipRendererType type, string layoutName, ShaderTypes t, uint hint, ShaderVar*[] variables ...)
338     {
339         import core.stdc.stdlib:malloc;
340         this.name = layoutName;
341         this.nameZeroEnded = (layoutName~"\0").ptr;
342         this.shaderType = t;
343         this.hint = hint;
344 
345         switch(type)
346         {
347             case HipRendererType.GL3:
348                 // if(hint & ShaderHint.GL_USE_STD_140)
349                     packFunc = &glSTD140;
350                 break;
351             case HipRendererType.D3D11:
352                 // if(hint & ShaderHint.D3D_USE_HLSL_4)
353                     packFunc = &dxHLSL4;
354                 break;
355             case HipRendererType.Metal:
356                 packFunc = &glSTD140;
357                 break;
358             case HipRendererType.None:
359             default:break;
360         }
361         if(packFunc is null) packFunc = &nonePack;
362 
363         foreach(ShaderVar* v; variables)
364         {
365             if(v.shaderType != t)
366                 throw new Exception("ShaderVariableLayout must contain only one shader type");
367             if(v.name in this.variables)
368                 throw new Exception("Variable named "~v.name~" is already in the layout "~name);
369             this.variables[v.name] = ShaderVarLayout(v, 0, 0);
370             namesOrder~= v.name;
371         }
372         if(variables.length > 0)
373         {
374             calcAlignment();
375             data = malloc(getLayoutSize());
376             if(data == null)
377                 throw new Exception("Out of memory");
378         }
379     }
380 
381     const(char)* nameStringz() const
382     {
383         return this.nameZeroEnded;
384     }
385 
386     static ShaderVariablesLayout from(T)(HipRendererInfo info)
387     {
388         enum attr = __traits(getAttributes, T);
389         static if(is(typeof(attr[0]) == HipShaderVertexUniform))
390             enum shaderType = ShaderTypes.vertex;
391         else static if(is(typeof(attr[0]) == HipShaderFragmentUniform))
392             enum shaderType = ShaderTypes.fragment;
393         else static assert(false,
394             "Type "~T.stringof~" doesn't have a HipShaderVertexUniform nor " ~
395             "HipShaderFragmentUniform attached to it."
396         );
397         static assert(
398             attr[0].name !is null,
399             "HipShaderUniform "~T.stringof~" must contain a name as it is required to work in Direct3D 11"
400         );
401         ShaderVariablesLayout ret = new ShaderVariablesLayout(info.type, attr[0].name, shaderType, 0);
402         static foreach(mem; __traits(allMembers, T))
403         {{
404             alias member = __traits(getMember, T.init, mem);
405             alias a = __traits(getAttributes, member);
406             static if(is(typeof(a[0]) == ShaderHint) && a[0] & ShaderHint.Blackbox)
407             {
408                 size_t length = 1;
409                 ret.appendBlackboxed(mem, uniformTypeFrom!(typeof(member)), info, length, a[0]);
410             }
411             else
412             {
413                 ret.append(mem, __traits(getMember, T.init, mem));
414             }
415 
416         }}
417 
418         return ret;
419     }
420 
421     IShader getShader(){return owner;}
422     void lock(IShader owner)
423     {
424         calcAlignment();
425         this.owner = owner;
426         this.isLocked = true;
427     }
428 
429     /**
430     *   Calculates the shader variables alignment based on the packFunc passed at startup.
431     *   Those functions are based on the shader vendor and version. Align should be called
432     *   always when there is a change on the layout.
433     */
434     final void calcAlignment()
435     {
436         size_t lastAlign = 0;
437         for(int i = 0; i < namesOrder.length; i++)
438         {
439             ShaderVarLayout* l = &variables[namesOrder[i]];
440             VarPosition pos = packFunc(l.sVar, lastAlign, i == cast(int)namesOrder.length-1);
441             l.size = pos.size;
442             l.alignment = pos.startPos;
443             lastAlign = pos.endPos;
444         }
445         lastPosition = lastAlign;
446     }
447 
448 
449     void* getBlockData()
450     {
451         import core.stdc.string:memcpy;
452         foreach(v; variables)
453             memcpy(data+v.alignment, v.sVar.data.ptr, v.size);
454         return data;
455     }
456 
457     protected ShaderVariablesLayout append(string varName, ShaderVar* v)
458     {
459         import core.stdc.stdlib:realloc;
460         if(varName in variables)
461             throw new Exception("Variable named "~varName~" is already in the layout "~name);
462         if(isLocked)
463             throw new Exception("Can't append ShaderVariable after it has been locked");
464         variables[varName] = ShaderVarLayout(v, 0, 0);
465         assert(varName in variables, "Could not set into variables?");
466         assert(variables[varName].sVar == v, "Could not set into variables?");
467         namesOrder~= varName;
468         calcAlignment();
469 
470         // import std.stdio; //FIXME: PROBLEM ON WASM
471         // writeln("Created var ", varName, " with hints ", cast(int)v.flags);
472 
473         this.data = realloc(this.data, getLayoutSize());
474         if(!this.data)
475             throw new Exception("Out of memory");
476         return this;
477     }
478 
479     /**
480     *   Appends a new variable to this layout.
481     *   Type is inferred.
482     */
483     ShaderVariablesLayout append(T)(string varName, T data)
484     {
485         return append(varName, ShaderVar.create(this.shaderType, varName, data, this));
486     }
487     /**
488     *   Appends a new variable to this layout.
489     *   Type is inferred.
490     */
491     ShaderVariablesLayout appendBlackboxed(string varName, UniformType t, HipRendererInfo info, size_t count, ShaderHint extraFlags)
492     {
493         size_t uSize = info.uniformMapper(shaderType, t);
494         if(uSize == 0)
495         {
496             unusedBlackboxed~= varName;
497             return this;
498         }
499 
500         ShaderVar* sV = ShaderVar.createEmpty(this.shaderType, varName, t, uSize*count, uSize, this);
501         if((extraFlags & ShaderHint.MaxTextures) != 0)
502             sV.setDirty();
503         sV.flags|= extraFlags;
504         sV.flags|= ShaderHint.Blackbox;
505 
506         return append(varName, sV);
507     }
508     /**
509     *   For speed sake, it doesn't check whether it is valid.
510     *   That means both a valid and invalid variable would return false (meaning used.)
511     */
512     bool isUnused(string varName) @nogc const
513     {
514         foreach(v; unusedBlackboxed) if(v == varName) return true;
515         return false;
516     }
517 
518     final size_t getLayoutSize(){return lastPosition;}
519     final void setAdditionalData(void* d, bool isAllocated)
520     {
521         this.additionalData = d;
522         this.isAdditionalAllocated = isAllocated;
523     }
524     final const(void*) getAdditionalData() const {return cast(const(void*))additionalData;}
525 
526     auto opDispatch(string member)()
527     {
528         return variables[member].sVar;
529     }
530 
531     void dispose()
532     {
533         import core.stdc.stdlib:free;
534         foreach (ref v; variables)
535         {
536             v.sVar.dispose();
537             v.alignment = 0;
538             v.size = 0;
539             v.sVar = null;
540         }
541         if(data != null)
542             free(data);
543         if(isAdditionalAllocated && additionalData != null)
544             free(additionalData);
545         additionalData = null;
546         data = null;
547     }
548 }
549 
550 
551 private bool isShaderVarNameValid(ref string varName)
552 {
553     import hip.util.string : indexOf;
554 
555     return varName.length > 0 &&
556     varName.indexOf(" ") == -1;
557 }